package cn.tedu.mall.seckill.timer.job;

import cn.tedu.mall.pojo.seckill.model.SeckillSku;
import cn.tedu.mall.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.seckill.mapper.SeckillSkuMapper;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SeckillInitialJob implements Job {
    @Autowired
    private SeckillSpuMapper seckillSpuMapper;
    @Autowired
    private SeckillSkuMapper seckillSkuMapper;
    //RedisTemplate在保存对象时，会将对象序列化成二进制格式，再将二进制格式保存在Redis中，
    // 这样做读写速度快，但是不能在Redis中修改数据
    @Autowired
    private RedisTemplate redisTemplate; //用于保存随机码
    //我们在修改库存时，需要在Redis中进行修改才能避免超卖现象
    //库存数是数字，Redis支持直接在Redis内部对数字格式的数据进行增减，无需java代码
    //这就需要我们将库存数以字符串的形式保存到Redis中而不是使用二进制方式
    //StringRedisTemplate在保存对象时，是将数据以字符串的方法保存在Redis中，秒杀时，
    // 可以直接操作Redis获取库存和减少库存的操作，避免线程安全问题，防止超卖
    @Autowired
    private StringRedisTemplate stringRedisTemplate;//保存库存数

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //本方法运行时秒杀还没有开始，要做的是将预热信息保存到Redis中
        //我们设计秒杀开始前5分钟进行预热
        //所以我们需要一个5分钟后的时间去检查秒杀是否开始
        LocalDateTime time = LocalDateTime.now().plusMinutes(5);
        //查询这个时间的所有秒杀商品
        List<SeckillSpu> seckillSpus = seckillSpuMapper.findSeckillSpusByTime(time);
        //如果seckillSpus为null，就说明不在时间范围内，不预热
        //如果不为null遍历seckillSpus，将他们的库存缓存到Redis中
        for (SeckillSpu spu : seckillSpus) {
            //库存数在sku中
            List<SeckillSku> seckillSkus = seckillSkuMapper.findSeckillSkusBySpuId(spu.getSpuId());
            //将当前spu对应的所有sku列表中对象的库存数保存在Redis中
            for (SeckillSku sku : seckillSkus) {
                log.info("开始将{}号商品的库存数预热到Redis中",sku.getSkuId());
                //Redis中的key我们利用工具类获取
                String stockKey = SeckillCacheUtils.getStockKey(sku.getSkuId());
                //检查Redis中是否已经包含这个key
                if (redisTemplate.hasKey(stockKey)){
                    log.info("{}号商品已经在缓存中",sku.getSkuId());
                } else {
                    //如果key不在Redis中，那么就需要将当前的库存数缓存到Redis中
                    stringRedisTemplate.boundValueOps(stockKey).set(sku.getSeckillStock()+"",
                            24*60*60+ RandomUtils.nextInt(100), TimeUnit.SECONDS);
                    log.info("成功为{}号商品添加库存",sku.getSkuId());
                }
            }
            //随机码的作用：要想访问商品必须通过控制层的url访问
            //例如：/seckill/spu/{id}，如果{id}的位置使用真实的id值，那么很容易猜到，就可以在秒杀开始前就购买了
            //这种行为肯定不行，可以将{id}位置的值设置成一个随机的数字，猜中的几率会降低
            //秒杀前5分钟才生成这个随机码，有了随机码才能购买这个商品
            //获取key值
            String randCodeKey = SeckillCacheUtils.getRandCodeKey(spu.getSpuId());
            //判断随机码是否已经生成
            if(!redisTemplate.hasKey(randCodeKey)){
                //如果随机码key为空，生成随机码
               int randomCode =  RandomUtils.nextInt(900000)+ 100000;
               redisTemplate.boundValueOps(randCodeKey).set(randomCode,
                       24*60*60+RandomUtils.nextInt(100),TimeUnit.SECONDS);
               log.info("spuId为{}的商品随机码为{}",spu.getSpuId(),randomCode);
            } else {
                //如果随机码已经存在就取出使用，但是实际情况下这里并不需要输出随机码，为了方便测试才输出的
                int randomCode = (int) redisTemplate.boundValueOps(randCodeKey).get();
                log.info("spuId为{}的商品随机码为{}",spu.getSpuId(),randomCode);
            }
        }
    }
}
